/* Merges libdocs (xml files); their name must be of the format <version>_<whatyouwant>.<whatyouwant>, in example 350_libdoc_20081010.xml and 100_libdoc.xml. It adds a child to all the modules, libraries, functions and variables to know in which firmware the functions, etc. are included. 
Usage: ./merge <file1.xml> <file2.xml> ... (it outputs a new file named out.xml) */

#include <string.h>

#include <libxml/parser.h>

#define forEachChild(lastnode, curnode) for(curnode = lastnode->xmlChildrenNode; curnode != NULL; curnode = curnode->next)
#define hasGoodName(node, nodeName) (!xmlStrcmp(node->name, (const xmlChar*)nodeName))

xmlNodePtr finalRoot;
int funcId = 0;
int libId = 0;
int prxId = 0;
int varId = 0;

void addVersionToNode(xmlNodePtr node, const char *firmware)
{
    xmlNodePtr versions, version;
    xmlNodePtr child;
    forEachChild(node, child)
        if (hasGoodName(child, "VERSIONS"))
            return;
    versions = xmlNewNode(NULL, (const xmlChar*)"VERSIONS");
    version = xmlNewNode(NULL, (const xmlChar*)"VERSION");
    xmlNodeSetContent(version, (const xmlChar*)firmware);
    xmlAddChild(versions, version);
    xmlAddChild(node, versions);
}

void addIdToNode(xmlNodePtr node, int id)
{
    char str[50] = {'\0'};
    snprintf(str, 49, "%d", id);
    xmlSetProp(node, (const xmlChar*)"id", (const xmlChar*)str);
}

void addVersionToFuncs(xmlNodePtr lib, const char *firmware)
{
    xmlNodePtr libContent, func;
    forEachChild(lib, libContent)
    {
        if (hasGoodName(libContent, "FUNCTIONS"))
        {
            forEachChild(libContent, func)
            {
                if (hasGoodName(func, "FUNCTION")) {
                    addVersionToNode(func, firmware);
                    addIdToNode(func, funcId++);
                }
            }
        }
    }
}

void addVersionToVars(xmlNodePtr lib, const char *firmware)
{
    xmlNodePtr libContent, var;
    forEachChild(lib, libContent)
    {
        if (hasGoodName(libContent, "VARIABLES"))
        {
            forEachChild(libContent, var)
            {
                if (hasGoodName(var, "VARIABLE")) {
                    addVersionToNode(var, firmware);
                    addIdToNode(var, varId++);
                }
            }
        }
    }
}

void addVersionToLibs(xmlNodePtr prx, const char *firmware)
{
    xmlNodePtr prxContent, lib;
    forEachChild(prx, prxContent)
    {   
        if (hasGoodName(prxContent, "LIBRARIES"))
        {   
            forEachChild(prxContent, lib)
            {
                if (hasGoodName(lib, "LIBRARY"))
                {
                    addVersionToNode(lib, firmware);
                    addVersionToFuncs(lib, firmware);
                    addVersionToVars(lib, firmware);
                    addIdToNode(lib, libId++);
                }
            }
        }
    }
}

void loadFile(xmlNodePtr root, const char *firmware)
{
    finalRoot = root;
    forEachChild(root, root)
    {
        if (hasGoodName(root, "PRXFILE"))
        {
            addVersionToNode(root, firmware);
            addVersionToLibs(root, firmware);
            addIdToNode(root, prxId++);
        }
    }
}

xmlNodePtr findPrxInTree(xmlNodePtr tree, const xmlChar *prxName)
{
    xmlNodePtr prxContent, prx;
    forEachChild(tree, tree)
    {
        if (hasGoodName(tree, "PRXFILE"))
        {
            prx = tree;
            forEachChild(tree, prxContent)
            {
                if (hasGoodName(prxContent, "PRX"))
                {
                    xmlChar *curName = xmlNodeGetContent(prxContent);
                    if (xmlStrcmp(prxName, curName) == 0) {
                        xmlFree(curName);
                        return prx;
                    }
                    xmlFree(curName);
                }
            }
        }
    }
    return NULL;
}

void prxAddInTree(xmlNodePtr newPrx, xmlNodePtr tree, const xmlChar *prxName, const char *firmware)
{
    xmlNodePtr prxContent, prxFile;
    prxFile = findPrxInTree(tree, prxName);
    if (prxFile)
    {
        forEachChild(prxFile, prxContent)
        {
            if (hasGoodName(prxContent, "VERSIONS"))
            {
                xmlNodePtr version = xmlNewNode(NULL, (const xmlChar*)"VERSION");
                xmlNodeSetContent(version, (const xmlChar*)firmware);
                xmlAddChild(prxContent, version);
                return;
            }
        }
    }
    else
    {
        /* PRX not found */
        xmlNodePtr newNode;
        newNode = xmlCopyNode(newPrx, 1);
        addVersionToLibs(newNode, firmware);
        addVersionToNode(newNode, firmware);
        addIdToNode(newNode, prxId++);
        xmlAddChild(tree, newNode);
    }
}

xmlNodePtr findLibInTree(xmlNodePtr tree, const xmlChar *prxName, const xmlChar *libName)
{
    xmlNodePtr prx = findPrxInTree(tree, prxName);
    xmlNodePtr prxContent, libs, lib, libContent;
    if (prx)
    {
        forEachChild(prx, prxContent)
        {
            if (hasGoodName(prxContent, "LIBRARIES"))
            {
                forEachChild(prxContent, libs)
                {
                    if (hasGoodName(libs, "LIBRARY"))
                    {
                        lib = libs;
                        forEachChild(libs, libContent)
                        {
                            if (hasGoodName(libContent, "NAME"))
                            {
                                xmlChar *curName = xmlNodeGetContent(libContent);
                                if (xmlStrcmp(libName, curName) == 0) {
                                    xmlFree(curName);
                                    return lib;
                                }
                                xmlFree(curName);
                            }
                        }
                    }
                }
            }
        }
    }
    return NULL;
}

void libAddInTree(xmlNodePtr newLib, xmlNodePtr tree, const xmlChar *prxName, const xmlChar *libName, const char *firmware)
{
    xmlNodePtr libContent, lib;
    lib = findLibInTree(tree, prxName, libName);
    if (lib)
    {
        forEachChild(lib, libContent)
        {
            if (hasGoodName(libContent, "VERSIONS"))
            {
                xmlNodePtr version = xmlNewNode(NULL, (const xmlChar*)"VERSION");
                xmlNodeSetContent(version, (const xmlChar*)firmware);
                xmlAddChild(libContent, version);
                return;
            }
        }
    }
    else
    {
        /* Library not found */
        xmlNodePtr prx = findPrxInTree(tree, prxName);
        if (prx)
        {
            xmlNodePtr newNode, prxContent;
            newNode = xmlCopyNode(newLib, 1);

            addVersionToFuncs(newNode, firmware);
            addVersionToVars(newNode, firmware);
            addVersionToNode(newNode, firmware);
            addIdToNode(newNode, libId++);
            forEachChild(prx, prxContent)
                if (hasGoodName(prxContent, "LIBRARIES"))
                    xmlAddChild(prxContent, newNode);
        }
        /* If prx is not found, it means it'll be copied entirely, so nothing is needed for now */
    }
}

xmlNodePtr findFuncInTree(xmlNodePtr tree, const xmlChar *prxName, const xmlChar *libName, const xmlChar *funcNid)
{
    xmlNodePtr lib = findLibInTree(tree, prxName, libName);
    xmlNodePtr libContent, funcs, func, funcContent;
    if (lib)
    {
        forEachChild(lib, libContent)
        {
            if (hasGoodName(libContent, "FUNCTIONS"))
            {
                forEachChild(libContent, funcs)
                {
                    if (hasGoodName(funcs, "FUNCTION"))
                    {
                        func = funcs;
                        forEachChild(funcs, funcContent)
                        {
                            if (hasGoodName(funcContent, "NID"))
                            {
                                xmlChar *curNid = xmlNodeGetContent(funcContent);
                                if (xmlStrcmp(funcNid, curNid) == 0) {
                                    xmlFree(curNid);
                                    return func;
                                }
                                xmlFree(curNid);
                            }
                        }
                    }
                }
            }
        }
    }
    return NULL;
}

void funcAddInTree(xmlNodePtr newFunc, xmlNodePtr tree, const xmlChar *prxName, const xmlChar *libName, const xmlChar *funcNid, const char *firmware)
{
    xmlNodePtr funcContent;
    xmlNodePtr func = findFuncInTree(tree, prxName, libName, funcNid);
    if (func)
    {
        forEachChild(func, funcContent)
        {
            if (hasGoodName(funcContent, "VERSIONS"))
            {
                xmlNodePtr version = xmlNewNode(NULL, (const xmlChar*)"VERSION");
                xmlNodeSetContent(version, (const xmlChar*)firmware);
                xmlAddChild(funcContent, version);
            }
        }
    }
    else
    {
        /* Function not found */
        xmlNodePtr lib = findLibInTree(tree, prxName, libName);
        if (lib)
        {
            xmlNodePtr newNode, libContent;
            newNode = xmlCopyNode(newFunc, 1);
            addVersionToNode(newNode, firmware);
            addIdToNode(newNode, funcId++);
            forEachChild(lib, libContent)
                if (hasGoodName(libContent, "FUNCTIONS"))
                    xmlAddChild(libContent, newNode);
        }
        /* If library is not found, it means it'll be copied entirely, so nothing is needed for now */
    }
}

xmlNodePtr findVarInTree(xmlNodePtr tree, const xmlChar *prxName, const xmlChar *libName, const xmlChar *varNid)
{
    xmlNodePtr lib = findLibInTree(tree, prxName, libName);
    xmlNodePtr libContent, vars, var, varContent;
    if (lib)
    {
        forEachChild(lib, libContent)
        {
            if (hasGoodName(libContent, "VARIABLES"))
            {
                forEachChild(libContent, vars)
                {
                    if (hasGoodName(vars, "VARIABLE"))
                    {
                        var = vars;
                        forEachChild(vars, varContent)
                        {
                            if (hasGoodName(varContent, "NID"))
                            {
                                xmlChar *curNid = xmlNodeGetContent(varContent);
                                if (xmlStrcmp(varNid, curNid) == 0) {
                                    xmlFree(curNid);
                                    return var;
                                }
                                xmlFree(curNid);
                            }
                        }
                    }
                }
            }
        }
    }
    return NULL;
}

void varAddInTree(xmlNodePtr newVar, xmlNodePtr tree, const xmlChar *prxName, const xmlChar *libName, const xmlChar *varNid, const char *firmware)
{
    xmlNodePtr varContent;
    xmlNodePtr var = findVarInTree(tree, prxName, libName, varNid);
    if (var)
    {
        forEachChild(var, varContent)
        {
            if (hasGoodName(varContent, "VERSIONS"))
            {
                xmlNodePtr version = xmlNewNode(NULL, (const xmlChar*)"VERSION");
                xmlNodeSetContent(version, (const xmlChar*)firmware);
                xmlAddChild(varContent, version);
            }
        }
    }
    else
    {
        /* Variable not found */
        xmlNodePtr lib = findLibInTree(tree, prxName, libName);
        if (lib)
        {
            xmlNodePtr newNode, libContent;
            newNode = xmlCopyNode(newVar, 1);
            addVersionToNode(newNode, firmware);
            addIdToNode(newNode, varId++);
            forEachChild(lib, libContent)
                if (hasGoodName(libContent, "VARIABLES"))
                    xmlAddChild(libContent, newNode);
        }
        /* If library is not found, it means it'll be copied entirely, so nothing is needed for now */
    }
}

void compFile(xmlNodePtr cur, const char *firmware)
{
    xmlNodePtr prx, prxContent, libs, lib, libContent, funcs, func, funcContent, vars, var, varContent;
    forEachChild(cur, cur)
    {
        if (hasGoodName(cur, "PRXFILE"))
        {
            xmlChar *prxName;
            prxContent = cur;
            forEachChild(cur, prx)
                if (hasGoodName(prx, "PRX"))
                    prxName = xmlNodeGetContent(prx);
            forEachChild(cur, prx)
            {
                if (hasGoodName(prx, "LIBRARIES"))
                {
                    forEachChild(prx, libs)
                    {
                        if (hasGoodName(libs, "LIBRARY"))
                        {
                            xmlChar *libName;
                            lib = libs;
                            forEachChild(libs, libContent)
                                if (hasGoodName(libContent, "NAME"))
                                    libName = xmlNodeGetContent(libContent);
                            forEachChild(libs, libContent)
                            {
                                if (hasGoodName(libContent, "FUNCTIONS"))
                                {
                                    forEachChild(libContent, funcs)
                                    {
                                        if (hasGoodName(funcs, "FUNCTION"))
                                        {
                                            func = funcs;
                                            forEachChild(funcs, funcContent)
                                            {
                                                if (hasGoodName(funcContent, "NID"))
                                                {
                                                    xmlChar *funcNid = xmlNodeGetContent(funcContent);
                                                    funcAddInTree(func, finalRoot, prxName, libName, funcNid, firmware);
                                                    xmlFree(funcNid);
                                                }
                                            }
                                        }
                                    }
                                }
                                if (hasGoodName(libContent, "VARIABLES"))
                                {
                                    forEachChild(libContent, vars)
                                    {
                                        if (hasGoodName(vars, "VARIABLE"))
                                        {
                                            var = vars;
                                            forEachChild(vars, varContent)
                                            {
                                                if (hasGoodName(varContent, "NID"))
                                                {
                                                    xmlChar *varNid = xmlNodeGetContent(varContent);
                                                    varAddInTree(var, finalRoot, prxName, libName, varNid, firmware);
                                                    xmlFree(varNid);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            libAddInTree(lib, finalRoot, prxName, libName, firmware);
                            xmlFree(libName);
                        }
                    }
                }
            }
            prxAddInTree(prxContent, finalRoot, prxName, firmware);
            xmlFree(prxName);
        }
    }

    /* for each common function => if VERSIONS exist, add firmware number, else create it with current firmware */
}

int main(int argc, char *argv[])
{
    xmlDocPtr doc, fullDoc;
    unsigned int numFiles = argc - 1;
    unsigned int i, j;

    if (argc == 1)
        printf("Please specify files\n");

    xmlKeepBlanksDefault(0);

    fullDoc = xmlParseFile(argv[1]);
    for (i = 0; i < numFiles; i++)
    {
        xmlNodePtr cur;
        char *name;
        for (j = 0; j < strlen(argv[i + 1]); j++)
            if (argv[i + 1][j] == '_')
                break;
        name = malloc(sizeof(char) * (j + 1));
        name[0] = '\0';
        strncpy(name, argv[i + 1], j);
        name[j] = '\0';
        printf("Reading %d: %s => %s\n", i, argv[i + 1], name);
        if (i == 0)
            doc = fullDoc;
        else
            doc = xmlParseFile(argv[i + 1]);
        cur = xmlDocGetRootElement(doc);
        if (cur == NULL || xmlStrcmp(cur->name, (const xmlChar*)"PSPLIBDOC"))
        {
            printf("File is not a valid libdoc! It doesn't have the 'PSPLIBDOC' category!\n");
            xmlFreeDoc(doc);
            continue;
        }

        for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next)
            if (!xmlStrcmp(cur->name, (const xmlChar*)"PRXFILES"))
                break;

        if (cur == NULL)
        {
            printf("File is not a valid libdoc! It doesn't have the 'PRXFILES' category (%p, %s, %d)!\n", cur, cur->name, (cur->type == XML_ELEMENT_NODE));
            xmlFreeDoc(doc);
            continue;
        }

        if (i == 0)
            loadFile(cur, name); /* First pass: add a VERSIONS in all the FUNCTION nodes */
        else {
            compFile(cur, name); /* Second pass: compare with previous file */
            xmlFreeDoc(doc);
        }
        free(name);
    }

    xmlSaveFormatFile("out.xml", fullDoc, 1);

    return 0;
}

